﻿using System;
using System.Diagnostics;

namespace Needle.Bus.Engine
{
    /// <summary>
    /// A Sample, usage: using (var sample = Sample.StartNew(subject) {  sample.Stop()}
    /// </summary>
    public class Sample : IDisposable
    {

        private int _endCount;
        private Stopwatch _watch;
        private IHasCount _subject;

        /// <summary>
        /// The difference between the subjects Count value at the start and the
        /// end of the sample.
        /// </summary>
        public int Delta { get { return _endCount - Count; } }
        /// <summary>
        /// The subjects Count value at the start of the sample.
        /// </summary>
        public int Count { get; private set; }
        /// <summary>
        /// The tick counter value at the start of the sample.
        /// </summary> 
        public long Timestamp { get; private set; }
        /// <summary>
        /// Gets the total elapsed time measured by the current instance, in timer ticks.
        /// </summary>
        public long Ticks { get { return _watch.ElapsedTicks; } }

        public static Sample StartNew(IHasCount subject)
        {
            Sample newSample = new Sample(subject);
            newSample.Start();
            return newSample;
        }

        public static Sample StartNew(Func<int> subject)
        {
            Sample newSample = new Sample(subject);
            newSample.Start();
            return newSample;
        }

        public Sample(Func<int> countSource)
            : this(new FuncWrapper(countSource))
        {
            if (countSource == null)
                throw new ArgumentNullException("countSource", "countSource is null.");
        }

        public Sample(IHasCount subject)
        {
            if (subject == null)
                throw new ArgumentNullException("subject", "subject is null.");
            _subject = subject;
        }

        public Sample Start()
        {
            Count = _endCount = _subject.Count;
            Timestamp = Stopwatch.GetTimestamp();
            _watch = Stopwatch.StartNew();
            return this;
        }

        public void Stop()
        {
            _endCount = _subject.Count;
            _watch.Stop();
        }

        public void RecordDelta(IPerformanceCounter perfCounter)
        {
            RecordDelta(perfCounter, false);
        }

        public void RecordDelta(IPerformanceCounter perfCounter, bool absolute)
        {
            if (perfCounter.IsEnabled)
            {
                Stop();
                perfCounter.Record(absolute ? Math.Abs(Delta) : Delta, Timestamp, Ticks);
                Start();
            }
        }
        public void RecordCount(IPerformanceCounter perfCounter)
        {
            perfCounter.Record(_subject.Count, Stopwatch.GetTimestamp());
        }

        void IDisposable.Dispose()
        {
            Stop();
        }

        private class FuncWrapper : IHasCount
        {
            Func<int> countSource;
            public FuncWrapper(Func<int> countSource) { this.countSource = countSource; }
            public int Count { get { return countSource(); } }
        }

    }
}
